/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.harmony.recyclerview.support;

import static com.harmony.recyclerview.support.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
import static com.harmony.recyclerview.support.ViewInfoStore.InfoRecord.FLAG_PRE;

import com.harmony.recyclerview.RecyclerComponent;
import com.harmony.recyclerview.annotation.VisibleForTesting;
import com.harmony.recyclerview.collection.LongSparseArray;
import com.harmony.recyclerview.collection.SimpleArrayMap;

/**
 * This class abstracts all tracking for Views to run animations.
 */
class ViewInfoStore {
    private static final boolean DEBUG = false;

    /**
     * View data records for pre-layout
     */
    @VisibleForTesting
    final SimpleArrayMap<RecyclerComponent.ViewHolder, InfoRecord> mLayoutHolderMap =
            new SimpleArrayMap<>();

    @VisibleForTesting
    final LongSparseArray<RecyclerComponent.ViewHolder> mOldChangedHolders = new LongSparseArray<>();

    /**
     * Clears the state and all existing tracking data
     */
    void clear() {
        mLayoutHolderMap.clear();
        mOldChangedHolders.clear();
    }

    boolean isDisappearing(RecyclerComponent.ViewHolder holder) {
        final InfoRecord record = mLayoutHolderMap.get(holder);
        return record != null && ((record.flags & FLAG_DISAPPEARED) != 0);
    }

    /**
     * Adds the given ViewHolder to the oldChangeHolders list
     *
     * @param key    The key to identify the ViewHolder.
     * @param holder The ViewHolder to store
     */
    void addToOldChangeHolders(long key, RecyclerComponent.ViewHolder holder) {
        mOldChangedHolders.put(key, holder);
    }

    /**
     * Checks whether the given ViewHolder is in preLayout list
     *
     * @param viewHolder The ViewHolder to query
     * @return True if the ViewHolder is present in preLayout, false otherwise
     */
    boolean isInPreLayout(RecyclerComponent.ViewHolder viewHolder) {
        final InfoRecord record = mLayoutHolderMap.get(viewHolder);
        return record != null && (record.flags & FLAG_PRE) != 0;
    }

    /**
     * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
     * null.
     *
     * @param key The key to be used to find the ViewHolder.
     * @return A ViewHolder if exists or null if it does not exist.
     */
    RecyclerComponent.ViewHolder getFromOldChangeHolders(long key) {
        return mOldChangedHolders.get(key);
    }

    /**
     * A ViewHolder might be added by the LayoutManager just to animate its disappearance.
     * This list holds such items so that we can animate / recycle these ViewHolders properly.
     *
     * @param holder The ViewHolder which disappeared during a layout.
     */
    void addToDisappearedInLayout(RecyclerComponent.ViewHolder holder) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.flags |= FLAG_DISAPPEARED;
    }

    /**
     * Removes a ViewHolder from disappearing list.
     *
     * @param holder The ViewHolder to be removed from the disappearing list.
     */
    void removeFromDisappearedInLayout(RecyclerComponent.ViewHolder holder) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            return;
        }
        record.flags &= ~FLAG_DISAPPEARED;
    }

    /**
     * Removes the ViewHolder from all list
     *
     * @param holder The ViewHolder which we should stop tracking
     */
    void removeViewHolder(RecyclerComponent.ViewHolder holder) {
        for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
            if (holder == mOldChangedHolders.valueAt(i)) {
                mOldChangedHolders.removeAt(i);
                break;
            }
        }
        final InfoRecord info = mLayoutHolderMap.remove(holder);
        if (info != null) {
            InfoRecord.recycle(info);
        }
    }

    void onDetach() {
        InfoRecord.drainCache();
    }

    /**
     * onViewDetached
     * @param viewHolder ViewHolder
     */
    public void onViewDetached(RecyclerComponent.ViewHolder viewHolder) {
        removeFromDisappearedInLayout(viewHolder);
    }

    static class InfoRecord {
        // disappearing list
        static final int FLAG_DISAPPEARED = 1;

        // appear in pre layout list
        static final int FLAG_APPEAR = 1 << 1;

        // pre layout, this is necessary to distinguish null item info
        static final int FLAG_PRE = 1 << 2;

        // post layout, this is necessary to distinguish null item info
        static final int FLAG_POST = 1 << 3;

        static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
        static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
        static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;

        static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);

        int flags;

        private InfoRecord() {
        }

        static InfoRecord obtain() {
            InfoRecord record = sPool.acquire();
            return record == null ? new InfoRecord() : record;
        }

        static void recycle(InfoRecord record) {
            record.flags = 0;
            sPool.release(record);
        }

        static void drainCache() {
            // noinspection StatementWithEmptyBody
            while (sPool.acquire() != null) ;
        }
    }
}
